package ru.amse.baltijsky.javascheme.tree;

import ru.amse.baltijsky.javascheme.node.AbstractNode;
import ru.amse.baltijsky.javascheme.nodeshape.INodeShape;
import ru.amse.baltijsky.javascheme.nodeshape.NoShape;

/**
 * A node of the schema tree that corresponds to some object in the parsed client source code, like, for instance,
 * Java If or While constructions. Contains information about the type of the object the node corresponds to, and the
 * code which it represents. Serves as the root of the hierarchy of schema tree nodes, each new level for the different
 * class of source code objects.
 */
public abstract class SchemaNode extends AbstractNode<SchemaNode> implements ILabeledNode {
    /**
     * A piece of code associated with the node.
     */
    protected String[] code = null;

    /**
     * A factory for schema tree nodes. It takes the type of the node and creates an instance of the appropriate
     * subclass of <code>Node</code>.
     *
     * @param type   the type of the node to be constructed
     * @param parent the parent of the newly constructed node
     * @param prev   the node that is previous for the newly created node in the terms of program execution flow
     * @param s      a piece of code associated with the newly created node
     * @return the created node or null if the type is unkonwn
     */
    public static SchemaNode createNode(NodeType type, SchemaNode parent, SchemaNode prev, String s[]) {
        switch (type) {
        case ACTION:
            return new ActionNode(parent, prev, s);
        case BLOCK:
            return new BlockNode(parent, prev, s);
        case IF:
            return new IfNode(parent, prev, s);
        case SWITCH:
            return new SwitchNode(parent, prev, s);
        case FOR:
            return new ForNode(parent, prev, s);
        case WHILE:
            return new WhileNode(parent, prev, s);
        case DO_WHILE:
            return new DoWhileNode(parent, prev, s);
        case BREAK:
            return new BreakNode(parent, prev, s);
        case THROW:
            return new ThrowNode(parent, prev, s);
        case RETURN:
            return new ReturnNode(parent, prev, s);
        case CONTINUE:
            return new ContinueNode(parent, prev, s);
        case SYNCHRONIZED:
            return new SynchronizedNode(parent, prev, s);
        case TRY:
            return new TryNode(parent, prev, s);
        case CATCH:
            return new CatchNode(parent, prev, s);
        case FINALLY:
            return new FinallyNode(parent, prev, s);
        case UNIT_BLOCK:
            return new UnitBlockNode(parent, prev, s);
        case CLASS:
            return new ClassNode(parent, prev, s);
        case METHOD:
            return new MethodNode(parent, prev, s);
        case TRANSLATION_UNIT:
            return new TranslationUnitNode(parent, prev, s);
        }
        return null;
    }

    /**
     * Default constructor. Creates a node without previous or parent (nesting) node and without associated code.
     */
    public SchemaNode() {
        this(null, null, null);
    }

    /**
     * Constructor. Initializes the nesting link and sets <code>.next</code> link in the previous node.
     *
     * @param parentNode the node that nests our node
     * @param prevNode   the node for which to set the <code>.next</code> link
     */
    public SchemaNode(SchemaNode parentNode, SchemaNode prevNode) {
        this(parentNode, prevNode, null);
    }

    /**
     * Constructs a new node without previous or nesting node and with information about the corresponding code.
     *
     * @param s code associated with the node
     */
    public SchemaNode(String[] s) {
        this(null, null, s);
    }

    /**
     * Constructs a new node with full information about the nesting and previous nodes and the corresponding code.
     *
     * @param parentNode link to the parent (nesting) node
     * @param prevNode   link to the node that is previous in the sense of program flow
     * @param s          code associated with the node
     */
    public SchemaNode(SchemaNode parentNode, SchemaNode prevNode, String[] s) {
        setParent(parentNode);

        if (prevNode != null) {
            prevNode.setNext(this);
        }
        code = s;
    }

    /**
     * Returns the type of the language construction represented by the node.
     *
     * @return the type of the language construction represented by the node.
     */
    public abstract NodeType getNodeType();

    /**
     * Returns the shape that is characteristic to this type of node.
     *
     * @return
     */
    public abstract INodeShape getShape();

    /**
     * Returns closing shape. It makes sense only for nesting nodes and marks the end of the nested level.
     *
     * @return closing shape; NoShape is reture\ned by default
     */
    public INodeShape getClosingShape() {
        return new NoShape();
    }

    /**
     * Returns the code associated with the node.
     *
     * @return the code associated with the node (empty string if no information was provided at the construction).
     */
    public String[] getCode() {
        if (code != null) {
            return code;
        }
        return new String[]{""};
    }

    /**
     * Returns the code associated with the node as a single line with line delimiters.
     *
     * @return a <code>String</code> that contains all the code delimited by newlines
     */
    public String getCodeAsString() {
        StringBuilder sb = new StringBuilder();
        for (String s : code) {
            sb.append(s);
            sb.append(String.format("%n"));
        }
        return sb.toString();
    }

    public String getCodeSingleLine() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < code.length; ++i) {
            sb.append(code[i]);
            if (i < code.length - 1) {
                sb.append(" ");
            }
        }
        return sb.toString();
    }

    /**
     * Returns the code associated with the node prefixed with the specified <code>String</code>.
     *
     * @param prefix a <code>String</code> that is added as a prefix to all <code>String<code>s of the node code
     * @return the code associated with the node prefixed with the specified <code>String</code>
     *         (empty string if no information was provided at the construction).
     */
    public String[] getCode(String prefix) {
        if (code == null) {
            return new String[]{""};
        }

        String[] ret = new String[code.length];
        for (int i = 0; i < code.length; ++i) {
            ret[i] = prefix + code[i];
        }
        return ret;
    }

    @Override
    public void addChild(SchemaNode p) {
        super.addChild(p);
    }

    // STUB
    public String getLabel() {
        return "";
    }

    // STUB
    public void setLabel(String label) {

    }

    public <T, C, E extends Throwable> T accept(TreeVisitor<T, C, E> visitor, C context) throws E {
        return visitor.visit(this, context);
    }
}